#include "StdAfx.hpp"
#include "Types.hpp"

TypeConvRules::TypeConvRules()
{
	for (uint i = 0; i < RuleCount; i++)
		RuleArr[i] = TypeConv_Implicit_Warning;
}

const char * Type::TYPE_NAMES[] = {
	"auto",
	"void",
	"uint8",
	"uint16",
	"uint32",
	"uint64",
	"int8",
	"int16",
	"int32",
	"int64",
	"float",
	"double",
	"bool",
	"char",
	"string",
};

const uint Type::TYPE_CLASSES[] = {
	CLASS_NOT_EMPTY, // auto
	CLASS_NOT_EMPTY | CLASS_COMPILED, // void
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_INTEGER | CLASS_UNSIGNED, // uint8
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_INTEGER | CLASS_UNSIGNED, // uint16
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_INTEGER | CLASS_UNSIGNED, // uint32
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_INTEGER | CLASS_UNSIGNED, // uint64
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_INTEGER | CLASS_SIGNED, // int8
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_INTEGER | CLASS_SIGNED, // int16
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_INTEGER | CLASS_SIGNED, // int32
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_INTEGER | CLASS_SIGNED, // int64
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_REAL, // float
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL | CLASS_NUMERIC | CLASS_REAL, // double
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL, // bool
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL, // char
	CLASS_NOT_EMPTY | CLASS_COMPILED | CLASS_PHYSICAL, // string
	0 // TYPE_COUNT
};

bool Type::TypeIsRaw(TYPE type)
{
	switch (type)
	{
	case TYPE_UINT8: case TYPE_UINT16: case TYPE_UINT32: case TYPE_UINT64:
	case TYPE_INT8: case TYPE_INT16: case TYPE_INT32: case TYPE_INT64:
	case TYPE_FLOAT: case TYPE_DOUBLE:
	case TYPE_BOOL:
	case TYPE_CHAR:
		return true;
	case TYPE_STRING:
		return false;
	default:
		assert(0);
		return false;
	}
}

bool Type::operator == (const Type &rhs) const
{
	return m_Type == rhs.m_Type;
}

uint Type::GetSize() const
{
	switch (m_Type)
	{
	case TYPE_VOID:
	case TYPE_AUTO:
		return 0;
	
	case TYPE_UINT8:   return 1;
	case TYPE_UINT16:  return 2;
	case TYPE_UINT32:  return 4;
	case TYPE_UINT64:  return 8;
	case TYPE_INT8:    return 1;
	case TYPE_INT16:   return 2;
	case TYPE_INT32:   return 4;
	case TYPE_INT64:   return 8;
	case TYPE_FLOAT:   return sizeof(float);
	case TYPE_DOUBLE:  return sizeof(double);
	case TYPE_BOOL:    return 1;
	case TYPE_CHAR:    return 1;
	case TYPE_STRING:  return sizeof(string);
	
	default: assert(0); return 0;
	}
}

TypeConv Type::CheckConvFrom(const Type &srcType, const TypeConvRules &rules) const
{
	// Same types.
	if (srcType.GetType() == m_Type)
		return TypeConv_Implicit;

	// Anything can be cast to void.
	if (m_Type == TYPE_VOID)
		return TypeConv_Implicit;

	// Any to bool
	if (m_Type == TYPE_BOOL)
	{
		switch (srcType.GetType())
		{
		case TYPE_CHAR:
		case TYPE_STRING:
		case TYPE_UINT8: case TYPE_UINT16: case TYPE_UINT32: case TYPE_UINT64:
		case TYPE_INT8: case TYPE_INT16: case TYPE_INT32: case TYPE_INT64:
		case TYPE_FLOAT:
		case TYPE_DOUBLE:
			return rules.RuleArr[TypeConvRules::Rule_Any_to_Bool];
		}
	}
	// Conversion to char
	if (m_Type == TYPE_CHAR)
	{
		switch (srcType.GetType())
		{
		case TYPE_STRING:
			return rules.RuleArr[TypeConvRules::Rule_String_to_Any];
		case TYPE_UINT8: case TYPE_UINT16: case TYPE_UINT32: case TYPE_UINT64:
		case TYPE_INT8: case TYPE_INT16: case TYPE_INT32: case TYPE_INT64:
		case TYPE_FLOAT:
		case TYPE_DOUBLE:
			return rules.RuleArr[TypeConvRules::Rule_Char_vs_Numeric];
			// I could also do min() here with Bigger_to_Smaller and Unsigned_vs_Signed for some of these types.
		case TYPE_BOOL:
			return (TypeConv)std::min<uint>(
				rules.RuleArr[TypeConvRules::Rule_Char_vs_Numeric],
				rules.RuleArr[TypeConvRules::Rule_Bool_to_Numeric]);
		}
	}
	// Conversion to string
	if (m_Type == TYPE_STRING)
	{
		switch (srcType.GetType())
		{
		case TYPE_CHAR:
			return TypeConv_Implicit;
		case TYPE_BOOL:
		case TYPE_UINT8: case TYPE_UINT16: case TYPE_UINT32: case TYPE_UINT64:
		case TYPE_INT8: case TYPE_INT16: case TYPE_INT32: case TYPE_INT64:
		case TYPE_FLOAT:
		case TYPE_DOUBLE:
			return rules.RuleArr[TypeConvRules::Rule_Any_to_String];
		}
	}

	// Bool to numeric
	if (srcType.GetType() == TYPE_BOOL)
	{
		switch (m_Type)
		{
		case TYPE_UINT8: case TYPE_UINT16: case TYPE_UINT32: case TYPE_UINT64:
		case TYPE_INT8: case TYPE_INT16: case TYPE_INT32: case TYPE_INT64:
		case TYPE_FLOAT:
		case TYPE_DOUBLE:
			return rules.RuleArr[TypeConvRules::Rule_Bool_to_Numeric];
		}
	}
	// Char to numeric
	if (srcType.GetType() == TYPE_CHAR)
	{
		switch (m_Type)
		{
		case TYPE_UINT8: case TYPE_UINT16: case TYPE_UINT32: case TYPE_UINT64:
		case TYPE_INT8: case TYPE_INT16: case TYPE_INT32: case TYPE_INT64:
		case TYPE_FLOAT:
		case TYPE_DOUBLE:
			return rules.RuleArr[TypeConvRules::Rule_Char_vs_Numeric];
		}
	}
	// String to anyting (parsing)
	// Bool is not here as casting string to bool means non-empty string, not parsing "false"/"true" (it was a hard decision though :)
	if (srcType.GetType() == TYPE_STRING)
	{
		switch (m_Type)
		{
		case TYPE_UINT8: case TYPE_UINT16: case TYPE_UINT32: case TYPE_UINT64:
		case TYPE_INT8: case TYPE_INT16: case TYPE_INT32: case TYPE_INT64:
		case TYPE_FLOAT:
		case TYPE_DOUBLE:
		case TYPE_CHAR:
			return rules.RuleArr[TypeConvRules::Rule_String_to_Any];
		}
	}

	// Between two integer types
	if (srcType.CheckClass(CLASS_INTEGER) && CheckClass(CLASS_INTEGER))
	{
		TypeConv result;
		if (srcType.CheckClass(CLASS_UNSIGNED) != CheckClass(CLASS_UNSIGNED))
		{
			if (srcType.CheckClass(CLASS_UNSIGNED) && srcType.GetSize() < GetSize())
				result = TypeConv_Implicit;
			else
				result = rules.RuleArr[TypeConvRules::Rule_Unsigned_vs_Signed];
		}
		else
			result = TypeConv_Implicit;

		if (GetSize() < srcType.GetSize())
			result = (TypeConv)std::min(result, rules.RuleArr[TypeConvRules::Rule_Bigger_to_Smaller]);

		return result;
	}

	// Between two real types
	if (m_Type == TYPE_DOUBLE && srcType.GetType() == TYPE_FLOAT)
		return TypeConv_Implicit;
	if (m_Type == TYPE_FLOAT && srcType.GetType() == TYPE_DOUBLE)
		return rules.RuleArr[TypeConvRules::Rule_Bigger_to_Smaller];

	// Between integer and real type
	if (CheckClass(CLASS_NUMERIC) && srcType.CheckClass(CLASS_NUMERIC))
		return rules.RuleArr[TypeConvRules::Rule_Real_vs_Integer];

	return TypeConv_None;
}

void Type::Promote(const Type &lhs, const Type &rhs)
{
	if (lhs == rhs)
		*this = lhs;
	else
	{
		if (lhs.GetType() == TYPE_STRING || rhs.GetType() == TYPE_STRING)
			Set(TYPE_STRING);
		else if (lhs.GetType() == TYPE_DOUBLE || rhs.GetType() == TYPE_DOUBLE)
			Set(TYPE_DOUBLE);
		else if (lhs.GetType() == TYPE_FLOAT || rhs.GetType() == TYPE_FLOAT)
			Set(TYPE_FLOAT);
		else if (lhs.CheckClass(Type::CLASS_INTEGER) || rhs.CheckClass(Type::CLASS_INTEGER))
		{
			uint lSize = lhs.GetSize(), rSize = rhs.GetSize();
			if (lSize >= rSize)
				*this = lhs;
			else
				*this = rhs;
		}
		else if (lhs.GetType() == TYPE_CHAR || rhs.GetType() == TYPE_CHAR)
			Set(TYPE_CHAR);
		else if (lhs.GetType() == TYPE_BOOL || rhs.GetType() == TYPE_BOOL)
			Set(TYPE_BOOL);
		else
			throw Error("Cannot promote types " + lhs.GetName_r() + " and " + rhs.GetName_r());
	}
}

void Type::AllocValue(void *addr) const
{
	if (IsRaw())
		return;

	switch (m_Type)
	{
	case TYPE_STRING:
		new (addr) string();
		break;
	default:
		assert(0);
	}
}

void Type::FreeValue(void *addr) const
{
	if (IsRaw())
		return;

	switch (m_Type)
	{
	case TYPE_STRING:
		((string*)addr)->~string();
		break;
	default:
		assert(0);
	}
}

void Type::CopyValue(void *dstAddr, const void *srcAddr) const
{
	if (IsRaw())
	{
		uint typeSize = GetSize();
		assert(typeSize);
		memcpy(dstAddr, srcAddr, typeSize);
	}
	else if (m_Type == TYPE_STRING)
		*(string*)dstAddr = *(const string*)srcAddr;
	else
		assert(0);
}

void Type::InitValue(void *addr) const
{
	if (IsRaw())
		ZeroMemory(addr, GetSize());
	else if (m_Type == TYPE_STRING)
	{
		// No additional initialization needed.
	}
	else
		assert(0);
}

bool Type::IsEquatable() const
{
	if (IsRaw())
		return true;
	if (m_Type == TYPE_STRING)
		return true;
	return false;
}

bool Type::ValuesEqual( const void *addr1, const void *addr2 ) const
{
	if (IsRaw())
		return memcmp(addr1, addr2, GetSize()) == 0;
	else if (m_Type == TYPE_STRING)
		return *(string*)addr1 == *(string*)addr2;
	else
	{
		assert(0); return false;
	}
}

void Type::GetName( string &out ) const
{
	out = TYPE_NAMES[m_Type];
}

void TypeVectorToStr( string &out, const std::vector<Type> &types )
{
	if (types.empty())
		out.clear();
	else
	{
		types[0].GetName(out);
		string tmp;
		for (uint i = 1; i < types.size(); i++)
		{
			types[i].GetName(tmp);
			out += ", ";
			out += tmp;
		}
	}
}
